﻿using System;
using System.Collections.Generic;
using System.ComponentModel.Composition;
using System.Data.Services.Client;
using System.Linq;
using System.Linq.Expressions;
using System.Reflection;
using System.Text.RegularExpressions;
using System.Threading;
using System.Windows;
using OrbitOne.CRMLyncInfoDashboard.CRM.OrganizationData;
using OrbitOne.CRMLyncInfoDashboard.Lync;

namespace OrbitOne.CRMLyncInfoDashboard.CRM.OData
{
    [Export(typeof(ICrmDataProvider))]
    public abstract class CrmDataProvider<T, TModel> : ICrmDataProvider
        where T : class
        where TModel : class, IEntityModel
    {
        private readonly IPredicateBuilder m_predicateBuilder;
        private readonly IClientScript m_clientScript;
        private readonly ICrmMetadataProvider m_crmMetadataProvider;
        private readonly MethodInfo m_methodInfo = typeof(string).GetMethods().First(mi => mi.Name == "Contains");
        protected readonly Regex m_phoneNumberCleanup = new Regex(@"\W");

        [ImportingConstructor]
        protected CrmDataProvider(IPredicateBuilder predicateBuilder,
            ICrmMetadataProvider crmMetadataProvider,
            IClientScript clientScript)
        {
            m_predicateBuilder = predicateBuilder;
            m_crmMetadataProvider = crmMetadataProvider;
            m_clientScript = clientScript;
        }

        public void GetMatchingEntities(IDataCollection viewModel, Endpoint[] endpoints)
        {
            viewModel.IsBusy = true;
            var predicate = m_predicateBuilder.GetPredicate<T>(LyncSearchFieldName, m_methodInfo, endpoints);
            var ctx = new LabContext();
            var query = (DataServiceQuery<T>)((DataServiceQuery<T>)ctx.GetEntitySet<T>()).Where(predicate);
            query.BeginExecute(ar => GetSearchResults(ar, viewModel), query);
        }

        public void GetAllEntities(ISearchFieldProvisionerDataCollection viewModel)
        {
            var ctx = new LabContext();
            
            var query = (DataServiceQuery<T>)ctx.GetEntitySet<T>();
            var pagingContext = new PagingContext<T>(ctx, query);
            var state = new GetEntitiesState<T>(LyncSearchFieldName, viewModel, pagingContext);
            query.BeginExecute(GetAllEntitiesComplete, state);
        }


        private void LogStuff(string stuff, ISearchFieldProvisionerDataCollection viewModel, Type type)
        {
           /*Deployment.Current.Dispatcher.BeginInvoke(
                () => viewModel.StatusMessages = viewModel
                                                     .StatusMessages
                                                     .Concat(new List<string>{string.Format("{0}: {1}", type.Name, stuff)}).ToList());*/
        }

        private void GetAllEntitiesComplete(IAsyncResult ar)
        {
                var state = ar.AsyncState as GetEntitiesState<T>;
                var context = state.Context;
                var query = context.Query;
                var lyncSearchFieldName = state.LyncSearchFieldName;
                var viewModel = state.DataCollection;

                LogStuff("Start GetAllEntitiesComplete", viewModel, typeof(T));

                QueryOperationResponse<T> response;

                if (query == null)
                {
                    response = (QueryOperationResponse<T>)context.ServiceContext.EndExecute<T>(ar);
                }
                else
                {
                    response = (QueryOperationResponse<T>)context.Query.EndExecute(ar);
                    context.Query = null;
                }

                var results = new DataServiceCollection<T>(response);

                LogStuff("Before PageProcessor Test", viewModel, typeof(T));

                if (context.PageProcessor != null && !context.PageProcessor(results))
                {
                    return;
                }

                LogStuff("After PageProcessor Test", viewModel, typeof(T));

                var token = results.Continuation;
                if (token == null)
                {
                    LogStuff("Continuation token is null", viewModel, typeof(T));
                    UpdateSearchField(state);
                    LogStuff("After UpdateSearchField", viewModel, typeof(T));
                    return;
                }
                LogStuff("Continuation token is not null", viewModel, typeof(T));
                context.ServiceContext.BeginExecute(token, GetAllEntitiesComplete, state);
        }

        private void UpdateSearchField(GetEntitiesState<T> state)
        {
            var pagingContext = state.Context;
            var lyncSearchFieldName = state.LyncSearchFieldName;
            var viewModel = state.DataCollection;
            var entities = state.Context.Results.ToList();

            LogStuff(lyncSearchFieldName, viewModel, typeof(T));


            var prop = typeof(T).GetProperty(lyncSearchFieldName);
               foreach (var entity in entities)
               {
                   LogStuff("Updating entity", viewModel, typeof(T));
                   string calculateSearchFieldValue = CalculateSearchFieldValue(entity);
                   prop.SetValue(entity, calculateSearchFieldValue, null);
                   LogStuff(calculateSearchFieldValue, viewModel, typeof(T));

               }

               LogStuff("Saving changes", viewModel, typeof(T));
               pagingContext.ServiceContext.BeginSaveChanges(SaveChangesOptions.ContinueOnError, SaveChangesComplete, state);
        }

        private void SaveChangesComplete(IAsyncResult ar)
        {
            
            var state = ar.AsyncState as GetEntitiesState<T>;
            var viewModel = state.DataCollection;
            
            LogStuff("Aquired state and viewmodel", viewModel, typeof(T));

            DataServiceResponse response = null;

           /* try
            {*/
                //response = state.Context.ServiceContext.EndSaveChanges(ar);
            /*}
            catch (Exception ex)
            {
                LogStuff(string.Format("ERROR: {0}", ex), viewModel, typeof(T));
            }*/

            /*LogStuff("Got response. Reading errors.", viewModel, typeof(T));
            LogStuff("Operation response count: " + response.Count(), viewModel, typeof(T));

            LogStuff(string.Join(",", response.Select(o => o.StatusCode.ToString())), viewModel, typeof (T));

            LogStuff("Saved changes", viewModel, typeof(T));*/

            Deployment.Current.Dispatcher.BeginInvoke(
                       () => viewModel.StatusMessages = viewModel
                                                            .StatusMessages
                                                            .Concat(new List<string>
                                                                         {
                                                                             string.Format("Entities of type {0} are updated.", typeof (T).Name)
                                                                         }).ToList());
        }

        public string CalculateSearchFieldValue(T entity)
        {
            return string.Join(",", GetSearchValues(entity).Where(s => !string.IsNullOrEmpty(s)));
        }

        protected abstract string[] GetSearchValues(T entity);


        protected string LyncSearchFieldName { get { return Application.Current.Resources[LyncSearchFieldResourceName].ToString(); } }
        protected abstract string LyncSearchFieldResourceName { get; }

        private void GetSearchResults(IAsyncResult ar, IDataCollection viewModel)
        {
            try
            {
                var query = (ar.AsyncState as DataServiceQuery<T>);

                if (query != null)
                {
                    var endExecute = query.EndExecute(ar);
                    var dataServiceCollection = new DataServiceCollection<T>(endExecute);
                    var entities = dataServiceCollection.ToList();
                    var models = GetEntityModels(entities);

                   // if (models.Count() > 0)
                    {
                        Deployment.Current.Dispatcher.BeginInvoke(() => viewModel.IsBusy = true);
                        m_crmMetadataProvider.RetrieveEntity(viewModel, models);
                    }
                    /*else
                    {
                        Deployment.Current.Dispatcher.BeginInvoke(() => viewModel.IsBusy = false);
                    }*/
                    return;
                }

                Log(viewModel, ar.AsyncState.ToString());
            }
            catch (Exception ex)
            {
                Log(viewModel, ex.ToString());
                throw;
            }
            return;
        }

        public void Log(IDataCollection viewModel, params string[] msg)
        {
            Expression<Func<IDataCollection, object>> target = vm => vm.Messages;
            SetProperty(target, viewModel, msg);
        }

        public static TModel[] GetEntityModels(IEnumerable<T> entities)
        {
            return entities.Select(e => e == null ? null : (TModel)Activator.CreateInstance(typeof(TModel), e)).ToArray();
        }

        public static void SetProperty<TIn, TOut>(Expression<Func<TIn, TOut>> expression, object obj, object value)
        {
            try
            {
                ((PropertyInfo)(GetMemberExpression(expression).Member)).SetValue(obj, value, null);
            }
            catch (Exception ex)
            {
                throw new Exception(expression.ToString(), ex);
            }
        }

        private static MemberExpression GetMemberExpression<TIn, TOut>(Expression<Func<TIn, TOut>> expression)
        {
            switch (expression.Body.NodeType)
            {
                case ExpressionType.MemberAccess:
                    return ((MemberExpression)(expression.Body));

                case ExpressionType.Call:
                    return ((MemberExpression)(((MethodCallExpression)(expression.Body)).Object));

                default:
                    return ((MemberExpression)(((UnaryExpression)(expression.Body)).Operand));
            }
        }


    }
}
